#!/usr/bin/python3
# -*- coding:utf-8 -*-
""""
@Author: 李吉凯
@Email: lijk34925@hundsun.com
@Time: 2022/6/20 19:14
@File: upload_script.py
"""

#!/usr/bin/python3
# -*- coding:utf-8 -*-
import os
import re
import traceback
import uuid

import jsonpath
import requests
import json
from BladeFerm.Lib.Common import logger
from BladeFerm.utils.SeleniumOperation import get_jsession
from BladeFerm.utils.basic_opration import is_chinese, get_curtime

class UploadScript():
    def __init__(self):
        self.scripts_dic = dict()
        self.headers = {
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Cookie": "SHIROSESSIONID=efead4a4-863d-43a4-acf8-155e64fd169a; sto-id-47873=GCAFBMKMFAAA; PHPSESSID=33946bf6d4a869d0414b6e78714de85d; tenantId=default; JSESSIONID=4F32A7529AA1A597020D6B42A198A56B; projectId=289aa8a63edf4ca4a664743d6e5e9c5c; vId=289aa8a63e; gdp_user_id=6f7cfd38-9e16-4ed8-8f58-ab649a963711; b3222f5ad5658c1a_gdp_gio_id=lijk34925; b3222f5ad5658c1a_gdp_cs1=lijk34925; b3222f5ad5658c1a_gdp_esid=10; fine_auth_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsaWprMzQ5MjUiLCJ0ZW5hbnRJZCI6ImRlZmF1bHQiLCJpc3MiOiJmYW5ydWFuIiwiZGVzY3JpcHRpb24iOiJbNjc0ZV1bNTQwOV1bNTFlZl0obGlqazM0OTI1KSIsImV4cCI6MTY1NTcyNjE2OSwiaWF0IjoxNjU1NzIyNTY5LCJqdGkiOiI4ckFhSVJ0N25HNlUzOUNSOHA1UXVTNFJlcjF0ZnJkM2ZhTThVZG42djlMaHZXeDAifQ.G5QP5TZZoaNyjpTxJHxbKiJUTOsz7MVJVqXrKyULATI",
            "Host": "cloud.blade.hundsun.com",
            "Origin": "http://cloud.blade.hundsun.com",
            "ProjectID": "289aa8a63edf4ca4a664743d6e5e9c5c",
            "Referer": "http://cloud.blade.hundsun.com/business/scriptDesign/busiScriptTree.htm",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
            "vId": "289aa8a63e"
        }

    def update_headers(self, ProjectID, Cookie):
        self.headers.update({'ProjectID': ProjectID, 'Cookie': Cookie})
        logger.info(f'Update headers: {self.headers}')


    def get_target_node_info(self, node_name=None):
        url = 'http://cloud.blade.hundsun.com/business/scriptDesign/getScriptTreeData.json'
        res = requests.post(url=url, headers=self.headers)
        logger.info(f'getScriptTreeData result: {res.text}')
        if res.status_code == 200:
            res_text = json.loads(res.text)
            target_nodes = jsonpath.jsonpath(res_text, f'$.[?(@.name=="{node_name}")]')
            logger.info(f'target_node: {target_nodes}')
            if len(target_nodes) == 1:
                target_node = target_nodes[0].get('id')
            else:
                logger.warning(f'节点名称 {node_name} 不唯一，不能唯一确定要导入到哪个节点下！')
                target_node = False
        else:
            logger.error(f'getScriptTreeData fail！')
            target_node = None
        return target_node

    def add_case_node(self, target_node, new_node):
        '''
        添加用例节点
        :param target_node:
        :param new_node:
        :return: {'bCaseTree':{'caseTreeId':''}, 'caseTreeId':''}
        '''
        url = 'http://cloud.blade.hundsun.com/business/testDesign/addZtreeData.json'
        params = {'sourceurl': 'business/testDesign/busiCaseTree',
                    'caseTreeId': ''.join(str(uuid.uuid4()).split('-')),
                    'updateTimeStr': '',
                    'treeCaseName': new_node,
                    'pId': target_node,
                    'action': 'addTreeNode',
                    'treeNodeType': 1,
                    'projectId': self.headers.get('ProjectID'),
                    'treeParentId': target_node
                  }
        res = requests.post(url=url, headers=self.headers, data=params)
        logger.info(f'add_case_node result: {res.text}')
        return json.loads(res.text)

    def add_cases(self, target_node, cases):
        url = "http://cloud.blade.hundsun.com/business/testDesign/caseStepCreateCase.json"
        params = {
            "sourceurl": "business/testDesign/busiCaseDesign",
            # 'nodePath': '金融反洗钱项目/反洗钱5.0/06.UI自动化测试/02.客户风险评估/奥数辅导',
            'caseName1': '奥数辅导',
            'caseType1': 0,
            'casePrior1': 1,
            'caseDetailType1': 0,
            'caseLayer': 0,
            'caseSideType1': 0,
            'parCaseTreeId': target_node,
            'noReview': 3,
            'caseName': '奥数辅导',
            'caseType': 2,
            'casePrior': 1,
            'caseDetailType': 0,
            'caseSideType': 0,
        }
        for k, v in cases.items():
            k_menu = k.split(".")[-1]
            for casename in v.split(","):
                casename = casename.strip()
                params["caseName1"] = k_menu + "_" + casename + "_" + get_curtime('%M%d%H%M%S')
                params["caseName"] = k_menu + "_" + casename + "_" + get_curtime('%M%d%H%M%S')
                logger.info(json.loads(json.dumps(params)))
                res = requests.post(url=url, headers=self.headers, data=json.loads(json.dumps(params)))
                res_info = json.loads(res.text)
                logger.info(res_info)

    def get_script_design_tree_data(self, caseId):
        '''
        获取脚本设计树的信息，返回目标节点下的子节点信息
        :param caseId:
        :return:
        '''
        url = 'https://blade.hundsun.com/api/v1/design/scriptDesign/getScriptDesignTreeData.json'
        data = {'treeParentId': caseId, 'caseTreeId': caseId,
                'createTimeOrder': 'create_time', 'treeCaseNameOrder': 'tree_case_name',
                'scriptNameOrder': 'script_name'}
        res = requests.post(url=url, headers=self.headers, data=data)
        res_data = json.loads(res.text)
        logger.info(f'getScriptDesignTreeData res_data: {res_data}')
        return res_data



    def add_script_node(self, target_node, new_node):
        '''
        添加节点，并返回添加后节点对应的id。若已经添加，则查询节点对应的id返回
        :param target_node: 父节点的id
        :param new_node: 当前节点的名称
        :return:
        '''
        url = 'http://cloud.blade.hundsun.com/business/scriptDesign/addScriptTreeData.json'
        caseId = ''.join(str(uuid.uuid4()).split('-'))    # 当前节点的id，随机生成的32位的uuid
        params = {'treeNodeType': 3, 'treeCaseName': new_node, 'treeParentId': target_node, 'caseTreeId': caseId}
        res = requests.post(url=url, headers=self.headers, data=params)
        res_data = json.loads(res.text)
        logger.info(f'add_script_node result: {res_data}')
        return_code = res_data.get('return_code', '-1')
        if return_code == '0':
            caseId = res_data.get('caseTreeId', '')
        elif return_code == '1':
            logger.warning(f'{new_node} 节点已经存在。')
            res_data = self.get_script_design_tree_data(target_node)
            res_jsonpath = jsonpath.jsonpath(res_data, f'$..[?(@.name=="{new_node}")]',)
            if res_jsonpath:
                caseId = res_jsonpath[0].get('id')
            else:
                caseId = ""
        else:
            caseId = ""
        return caseId

    def add_script(self, target_node, cases):
        '''
        上传脚本到blade的测试脚本上去
        :param cases: 添加的脚步参数。数据类型："客户信息管理.企业信息查询.企业信息查询页": "search_info,search_info1",
        :param target_node: 目标节点id
        :return:
        '''
        url = "http://cloud.blade.hundsun.com/business/testDesign/caseElement/addScript.json"
        params = {
            "sourceurl": "business/scriptDesign/busiScriptTree",
            "scriptName": "产品户信息查询页_reset",
            "scriptType": 47,
            "调用模块名": "客户信息管理.产品户信息查询.产品户信息查询页",
            "调用函数名": "reset",
            "子类型": "web应用",
            "超时时间": "60",
            # "精准标签": "",
            # "fileType": "",
            # "isRequired": "",
            # "uploadTypeControl": "",
            # "scriptFilePath": "",
            # "scriptFileName": "",
            # "scriptFileType": "",
            "scriptContent": "python|客户信息管理.产品户信息查询.产品户信息查询页,reset,web应用,60,",
            "scriptState": 0,
            "scriptRemark": "",
            # "scriptId": "",
            "caseTreeId": target_node,
            "scriptJsonData": {"调用模块名": "客户信息管理.产品户信息查询.产品户信息查询页", "调用函数名": "reset", "子类型": "web应用", "超时时间": "60",
                               "精准标签": ""}
        }
        for k, v in cases.items():
            k_menu = k.split(".")[-1]
            for casename in v.split(","):
                casename = casename.strip()
                script_name = k_menu + "_" + casename
                params["scriptName"] = script_name  # + "_" + get_curtime('%M%d%H%M%S')
                params["scriptRemark"] = script_name
                params["调用模块名"] = k
                params["调用函数名"] = casename
                params["scriptContent"] = "python|%s,%s,web应用,60," % (k, casename)
                params["scriptJsonData"] = '{"调用模块名": "%s", "调用函数名": "%s", "子类型": "web应用", "超时时间": "60", "精准标签": ""}' % (k, casename)
                logger.info(json.loads(json.dumps(params)))
                res_data2 = self.get_script_design_tree_data(target_node)
                res_jsonpath2 = jsonpath.jsonpath(res_data2, f'$..[?(@.name=="{script_name}")]')
                if res_jsonpath2:
                    logger.warning(f'{script_name} 已经存在，不再增加。')
                else:
                    res = requests.post(url=url, headers=self.headers, data=json.loads(json.dumps(params)))
                    res_info = json.loads(res.text)
                    logger.info(res_info)


    def get_script_methods(self, target_path: str):
        '''
        递归查找 pytest单元测试脚本中导入的模块名称和方法名称
        :param target_path:
        :return:
        '''
        names = os.listdir(target_path)
        for name in names:
            cur_path = os.path.join(target_path, name)
            logger.info(f'cur_path: {cur_path}')
            if not os.path.isfile(cur_path):
                target_path_new = os.path.join(target_path, name)
                self.get_script_methods(target_path_new)
            elif os.path.isfile(cur_path) and re.match(r'testUnit', name, re.I):
                target_path_file = os.path.join(target_path, name)
                with open(target_path_file, 'r', encoding='utf-8') as f:
                    lines = f.readlines()
                for i, line in enumerate(lines):
                    if line.startswith('from'):
                        res = re.match(r'from (.*?) import (.*?)\n', line)
                        modul, method = res[1], res[2]
                        if is_chinese(modul):
                            if method.endswith('\\'):
                                next_line = lines[i+1]
                                logger.debug(f'next_line: {next_line}')
                                method = method.rstrip('\\') + next_line.strip()
                            if self.scripts_dic.get(modul, None):
                                self.scripts_dic.update({modul: self.scripts_dic.get(modul) + ',' + method})
                            else:
                                self.scripts_dic[modul] = method
                            logger.info(f'脚本 {target_path_file} 中找到模块 {modul} 和方法 {method}。')
                    elif line.startswith('def '):
                        break
                    else:
                        continue
            else:
                continue

    def get_script_dic(self, modul=None, index=1):
        '''
        获取一级目录分类，并按照一级目录进行分类
        :param index: 下级目录的名称截取标志。0：取第1个，1：取第2个，...
        :return:
        '''
        res = []
        logger.info(f'scripts_dic: {self.scripts_dic}')
        for unit in self.scripts_dic.keys():
            dirs = unit.split('.')
            if len(dirs) > index:
                res.append(dirs[index])
            else:
                res.append(dirs[-1])
        res_second = list(set(res))
        logger.info(f'res_second: {res_second}')
        # 按照一级目录进行分类
        res_dict = dict()
        for unit in res_second:
            sec_dic = dict()
            for key, value in self.scripts_dic.items():
                logger.debug(f'unit: {unit}, key: {key}')
                if modul:
                    if re.search(unit, key, re.I) and re.search(modul, key, re.I):
                        sec_dic[key] = value
                else:
                    if re.search(unit, key, re.I):
                        sec_dic[key] = value
            if sec_dic:
                res_dict[unit] = sec_dic
            logger.info(f'add node {unit} and {sec_dic}')
        return res_dict

    def create_node_and_script(self, node_name, res_dict=None):
        res_node = self.get_target_node_info(node_name)
        if res_node:
            for key, ch_dic in res_dict.items():
                logger.info(f'{key}: {ch_dic}')
                node_info = self.add_script_node(res_node, key)
                logger.info(f'{res_node} 对应的caseTreeId： {node_info}')
                if node_info:
                    self.add_script(node_info, ch_dic)
        else:
            return False

    def upload_script(self, project_id, cookie, target_path, node_name, modul, index=1):
        self.update_headers(project_id, cookie)
        self.get_script_methods(target_path=target_path)
        res_dict = self.get_script_dic(modul, index=index)
        self.create_node_and_script(node_name=node_name, res_dict=res_dict)

    def create_node_and_cases(self, node_name, res_dict=None):
        res_node = self.get_target_node_info(node_name)
        if res_node:
            for key, ch_dic in res_dict.items():
                logger.info(f'{key}: {ch_dic}')
                node_info = self.add_script_node(res_node, key)
                logger.info(f'{res_node} 对应的caseTreeId： {node_info}')
                if node_info:
                    self.add_script(node_info, ch_dic)
        else:
            return False

    def upload_cases(self, project_id, cookie, target_path, node_name, modul, index=1):
        self.update_headers(project_id, cookie)
        self.get_script_methods(target_path=target_path)
        res_dict = self.get_script_dic(modul, index=index)
        self.create_node_and_cases(node_name=node_name, res_dict=res_dict)

def create_node_and_script(username, password, project, target_path, node_name, modul_name, index=1):
    '''
    读取本地脚本并上传的blade平台测试脚本库中。
    :param username: 登录blade的用户名
    :param password: 登录blade的密码
    :param password: 登录blade后切换项目
    :param target_path: 本地脚本的脚步路径，例如：E:\Web_AutoTest\Project\金融反洗钱项目\web应用 【包含text_xx.py的目录】
    :param node_name: blade上，测试脚本界面，要添加脚本的节点名称。可先自己新建（名称要唯一）
    :param modul_name: 上传脚本时，根据模块名称进行筛选。只能配置一级目录/模块名称进行筛选。一般填写导包路径【客户风险评估.风险等级查询.风险等级查询_查询】的第一个字段，如‘客户风险评估’
    :param index: 测试脚本，二级目录分类标志。从导包路径中进行分割，提取。例如：【客户风险评估.风险等级查询.风险等级查询_查询】
    :return:
    '''
    login_info = {'username': username, 'password': password, 'getToken': False, 'isClose': True,
                  'jira_url': 'https://blade.hundsun.com/blade/index', 'project': project
                  }
    res_info = {'upload_result': 0, 'msg': '', 'error': ''}
    try:
        cookie = get_jsession(login_info)
        project_id = re.search(r'projectId=(.*?);', cookie)[1]
        US = UploadScript()
        US.upload_script(project_id, cookie, target_path, node_name, modul_name, index=index)
        res_info.update({'upload_result': 1, 'msg': '上传成功'})
    except Exception as e:
        logger.error(e)
        traceback.print_exc()
        res_info.update({'msg': '上传失败', 'error': str(e)})
    finally:
        return res_info

if __name__ == '__main__':
    username = 'lijk34925'
    password = ''
    target_path = '/金融反洗钱项目/web应用/流程管理'
    node_name = '10.验证脚本上传功能'
    modul_name = '流程管理'
    create_node_and_script(username, password, target_path, node_name, modul_name, index=1)
